package com.ictravi.iamunit.connector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.ictravi.iamunit.IAMUnitException;
import com.ictravi.iamunit.connector.AbstractConnector;
import com.ictravi.iamunit.IAMUnitTestCase;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;

/**
 * EXECConnector, which executes commands locally or remotely
 * over SSH.
 * 
 * @author  Mark van Reijn, idfocus
 * @author  A. Louw, iCTraVi Holding B.V.
 * @version 1.0
 */
public class EXECConnector extends AbstractConnector {
	protected Map<String, String> config;

	private static final Object CONFIG_USER     = "username";
	private static final Object CONFIG_SERVER   = "server";
	private static final Object CONFIG_PORT     = "port";
	private static final Object CONFIG_PASSWORD = "password";
	// Max length of the commandline.
	private static final int CMD_BUFFER = 256;

	private static Log LOG = LogFactory.getLog(IAMUnitTestCase.class);

	/**
	 * Called at startup to initialize the connector
	 * 
	 * @since v1.0.0
	 * @param config
	 *            configuration for this connector
	 **/
	public void setup(Map<String, String> config) throws IAMUnitException {
		LOG.debug("...in: ExecConnector - Setup");
		this.config = config;

	}

	/**
	 * builds the commandline to exectute from the parameters
	 * 
	 * @since v1.0.0
	 * @param data
	 *            the data to create the commandline with
	 * @return String
	 *            String value for the commandline
	 **/
	private String buildCmdLine(Map<String, Collection<String>> data) {
		// FIXME use the values from config here
		Set<String> keySet = data.keySet();
		StringBuffer cmdData = new StringBuffer(CMD_BUFFER);
		for (Iterator<String> iter = keySet.iterator(); iter.hasNext();) {
			String attrName = iter.next();
			LOG.info("Processing key: ");
			LOG.info(attrName);

			// FIXME this will append all data in the order from the sheet.
			// Should check for command and arguments only
			Collection<String> attrVal = data.get(attrName);
			// Append the data to the data entry here
			for (Iterator<String> iter2 = attrVal.iterator(); iter2.hasNext();) {
				cmdData.append(iter2.next().trim());
				if (iter2.hasNext()) {
					cmdData.append(" ");
				}
			}
			if (iter.hasNext()) {
				cmdData.append(" ");
			}
		}
		return cmdData.toString();
	}

	/**
	 * opLocalCmd - runs a command on the local machine
	 * 
	 * @since v1.0.0
	 * @param data
	 *            attributeMap from sheet
	 **/
	public void opLocalCmd(Map<String, Collection<String>> data)
			throws IAMUnitException {
		String cmdLine = buildCmdLine(data);
		// FIXME Hardcoded. Should be a cwd.
		LOG.info("...executing commandline: " + cmdLine);
		Runtime run = Runtime.getRuntime();
		try {
			Process pr = run.exec(cmdLine);
			int exitStatus = pr.waitFor();
			LOG.info("Command finished with exit status " + exitStatus
					+ ", output: ");

			BufferedReader buf = new BufferedReader(new InputStreamReader(
					pr.getInputStream()));
			String line;
			while ((line = buf.readLine()) != null) {
				LOG.info(line);
			}

			if (exitStatus > 0) {
				throw new IAMUnitException("Command returned error status ("
						+ exitStatus + ")");
			}
		} catch (Exception e) {
			throw new IAMUnitException("Failed to execute " + cmdLine + ". Error: "
					+ e.getMessage());
		}
		LOG.info("...SUCCESS");

	}

	/**
	 * opRemoteCmd - runs a command on remote machine over SSH
	 * 
	 * @since v1.0.0
	 * @param data
	 *            attributeMap from sheet
	 **/
	public void opRemoteCmd(Map<String, Collection<String>> data)
			throws IAMUnitException {
		String cmdLine = buildCmdLine(data);
		LOG.info("...executing commandline: " + cmdLine);
		JSch jsch = new JSch();

		try {
			Session session;
			Integer port = Integer.parseInt(config.get(CONFIG_PORT));
			if (port > 0)
			{
				LOG.debug("...server port configured");
				session = jsch.getSession(config.get(CONFIG_USER), config.get(CONFIG_SERVER), port);				
			}
			else
			{
				LOG.debug("...server port not configured, using default.");
				session = jsch.getSession(config.get(CONFIG_USER), config.get(CONFIG_SERVER), 22);				
			}
			java.util.Properties configExec = new java.util.Properties();
			configExec.put("StrictHostKeyChecking", "no");
			session.setConfig(configExec);

			session.setPassword((String) config.get(CONFIG_PASSWORD));
			session.connect(10000);

			Channel channel = session.openChannel("exec");
			((ChannelExec) channel).setCommand(cmdLine);

			channel.setInputStream(null);
			((ChannelExec) channel).setErrStream(System.err);
			InputStream in = channel.getInputStream();

			channel.connect();

			int exitStatus;
			String output = "";

			byte[] tmp = new byte[1024];
			while (true) {
				while (in.available() > 0) {
					int i = in.read(tmp, 0, 1024);
					if (i < 0)
						break;
					output += new String(tmp, 0, i);
				}
				if (channel.isClosed()) {
					exitStatus = channel.getExitStatus();
					break;
				}
				try {
					Thread.sleep(1000);
				} catch (Exception ee) {
				}
			}
			channel.disconnect();
			session.disconnect();
			LOG.info("Command finished with exit status " + exitStatus);
			LOG.debug("output: " + output);

			if (exitStatus > 0) {
				throw new IAMUnitException("Command returned error status ("
						+ exitStatus + ")");
			}
		} catch (Exception e) {
			throw new IAMUnitException("Failed to execute " + cmdLine + ". Error: "
					+ e.getMessage());
		}
		LOG.info("...SUCCESS");
	}

}